﻿//-----------------------------------------------------------------------
// <copyright file="PrivateMessagesViewModel.cs" company="AFS">
// Copyright (c) AFS. All rights reserved.
//
//    This file is part of Chatbox.
//
//    Chatbox is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Lesser General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    any later version.
//
//    Chatbox is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//    GNU Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public License
//    along with Chatbox. If not, see http://www.gnu.org/copyleft/lesser.html
// </copyright>
//----------------------------------------------------------------------- 

namespace ChatBox.UI.Windows.PrivateMessages
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Windows;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Threading;

    using ChatBox.DomainObjects.ResultData;
    using ChatBox.DomainObjects.Settings;
    using ChatBox.DomainObjects.XmlFeedObjects;
    using ChatBox.Interfaces;
    using ChatBox.UI.MessageDisplay;
    using ChatBox.UI.MessageFilters;
    using ChatBox.UI.ViewModels;
    using ChatBox.UI.Windows.SendPrivateMessage;
    using ChatBox.Utility;
    using ChatBox.XamlUtility;

    /// <summary>
    /// View Model for the private messages form
    /// </summary>
    public class PrivateMessagesViewModel : WindowViewModel
    {
        #region data

        /// <summary>
        /// The chat functionality
        /// </summary>
        private readonly IChatEngine chatEngine;

        /// <summary>
        /// the source for Ui settings
        /// </summary>
        private readonly IUISettingsRepository uiSettingsRepository;

        /// <summary>
        /// the Source for Server settings
        /// </summary>
        private readonly IServerSettingsRepository serverSettingsRepository;

        /// <summary>
        /// Command to read settings
        /// </summary>
        private readonly ICommand privateMessagesReadSettingsCommand;

        /// <summary>
        /// Commnd to display messages
        /// </summary>
        private readonly ICommand displayPrivateMessagesDataCommand;

        /// <summary>
        /// settings as animated visiblity for binding
        /// </summary>
        private VisibilityAnimationState settingsVisibilityState = VisibilityAnimationState.Hiding;

        /// <summary>
        /// settings area visiblity for binding
        /// </summary>
        private Visibility settingsVisibility = Visibility.Collapsed;

        /// <summary>
        /// settings area visiblity for binding
        /// </summary>
        private bool isSettingsVisible;

        /// <summary>
        /// Is the bulk delete button enabled
        /// </summary>
        private bool bulkDeleteEnabled;

        /// <summary>
        /// Text to show at the bottom of the window
        /// </summary>
        private string statusText;

        /// <summary>
        /// Is the Ui in a state ready to apply a filter 
        /// </summary>
        private bool canFilter;

        /// <summary>
        /// Does the Ui have a filter at present
        /// </summary>
        private bool hasFilter;

        /// <summary>
        /// Visibility of the loading text
        /// </summary>
        private Visibility loadingTextVisiblity = Visibility.Visible;

        /// <summary>
        /// The document to display
        /// </summary>
        private FlowDocument messagesDocument;

        /// <summary>
        /// The mouse-over link preview
        /// </summary>
        private string linkPreviewText;

        #endregion

        /// <summary>
        /// Initializes a new instance of the PrivateMessagesViewModel class
        /// </summary>
        /// <param name="chatEngine">the chat engine to use</param>
        /// <param name="uiSettingsRepository">the source of Ui settings</param>
        /// <param name="serverSettingsRepository">the source of Server settings</param>
        /// <param name="filters">the filter view model</param>
        /// <param name="showSendPrivateMessageCommand">command to show the "send private message" window</param>
        /// <param name="privateMessagesReadSettingsCommand">command to read settings</param>
        /// <param name="displayPrivateMessagesDataCommand">command to display private messages</param>
        public PrivateMessagesViewModel(
            IChatEngine chatEngine, 
            IUISettingsRepository uiSettingsRepository,
            IServerSettingsRepository serverSettingsRepository,
            FilterViewModel filters,
            ShowSendPrivateMessageCommand showSendPrivateMessageCommand,
            PrivateMessagesReadSettingsCommand privateMessagesReadSettingsCommand,
            DisplayPrivateMessagesDataCommand displayPrivateMessagesDataCommand)
        {
            this.chatEngine = chatEngine;
            this.uiSettingsRepository = uiSettingsRepository;
            this.serverSettingsRepository = serverSettingsRepository;
            this.Filters = filters;
            this.ShowSendPrivateMessageCommand = showSendPrivateMessageCommand;
            this.privateMessagesReadSettingsCommand = privateMessagesReadSettingsCommand;
            this.displayPrivateMessagesDataCommand = displayPrivateMessagesDataCommand;

            // keep notification of operations completed on the chat engine
            this.chatEngine.OperationCompleted += this.ChatEngineOperationCompleted;
            this.chatEngine.PrivateMessagesReceived += this.ChatEnginePrivateMessagesReceived;
        }

        #region dependency properties

        /// <summary>
        /// Gets or sets the visiblity of the settings area
        /// </summary>
        public VisibilityAnimationState SettingsVisibilityState
        {
            get
            {
                return this.settingsVisibilityState;
            }

            set
            {
                if (this.settingsVisibilityState != value)
                {
                    this.settingsVisibilityState = value;
                    OnPropertyChanged("SettingsVisibilityState");

                    // don't make settings visible at startup just to hide it again. 
                    if (this.SettingsVisibilityState != VisibilityAnimationState.Hiding)
                    {
                        this.SettingsVisibility = value.ToVisibility();
                    }

                    this.IsSettingsVisible = value.IsVisibleState();
                }
            }
        }

        /// <summary>
        /// Gets the visiblity of the settings area
        /// </summary>
        public Visibility SettingsVisibility
        {
            get
            {
                return this.settingsVisibility;
            }

            private set
            {
                if (this.settingsVisibility != value)
                {
                    this.settingsVisibility = value;
                    OnPropertyChanged("SettingsVisibility");
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the settings area is visible
        /// </summary>
        public bool IsSettingsVisible
        {
            get
            {
                return this.isSettingsVisible;
            }

            set
            {
                if (this.isSettingsVisible != value)
                {
                    this.isSettingsVisible = value;
                    OnPropertyChanged("IsSettingsVisible");

                    if (value)
                    {
                        if (!this.SettingsVisibilityState.IsVisibleState())
                        {
                            this.SettingsVisibilityState = VisibilityAnimationState.Showing;
                        }
                    }
                    else
                    {
                        if (this.SettingsVisibilityState.IsVisibleState())
                        {
                            this.SettingsVisibilityState = VisibilityAnimationState.Hiding;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the bulk delete is enabled
        /// </summary>
        public bool BulkDeleteEnabled
        {
            get
            {
                return this.bulkDeleteEnabled;
            }

            set
            {
                if (this.bulkDeleteEnabled != value)
                {
                    this.bulkDeleteEnabled = value;
                    OnPropertyChanged("BulkDeleteEnabled");
                }
            }
        }

        /// <summary>
        /// Gets or sets the status text
        /// </summary>
        public string StatusText
        {
            get
            {
                return this.statusText;
            }

            set
            {
                if (this.statusText != value)
                {
                    this.statusText = value;
                    OnPropertyChanged("StatusText");
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the Ui is in a state ready to apply a filter 
        /// </summary>
        public bool CanFilter
        {
            get
            {
                return this.canFilter;
            }

            set
            {
                if (this.canFilter != value)
                {
                    this.canFilter = value;
                    OnPropertyChanged("CanFilter");
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the Ui has a filter at present
        /// </summary>
        public bool HasFilter
        {
            get
            {
                return this.hasFilter;
            }

            set
            {
                if (this.hasFilter != value)
                {
                    this.hasFilter = value;
                    OnPropertyChanged("HasFilter");
                }
            }
        }

        /// <summary>
        /// Gets or sets the visibility of the loading text
        /// </summary>
        public Visibility LoadingTextVisibility
        {
            get
            {
                return this.loadingTextVisiblity;
            }

            set
            {
                if (this.loadingTextVisiblity != value)
                {
                    this.loadingTextVisiblity = value;
                    OnPropertyChanged("LoadingTextVisibility");
                }
            }
        }

        /// <summary>
        /// Gets or sets the document to display
        /// </summary>
        public FlowDocument MessagesDocument
        {
            get
            {
                return this.messagesDocument;
            }

            set
            {
                if (this.messagesDocument != value)
                {
                    this.messagesDocument = value;
                    OnPropertyChanged("MessagesDocument");
                }
            }
        }

        /// <summary>
        /// Gets or sets the mouse-over link preview
        /// </summary>
        public string LinkPreviewText
        {
            get
            {
                return this.linkPreviewText;
            }

            set
            {
                if (this.linkPreviewText != value)
                {
                    this.linkPreviewText = value;
                    OnPropertyChanged("LinkPreviewText");
                }
            }
        }

        #endregion

        #region other properties

        /// <summary>
        /// Gets the chat engine
        /// </summary>
        public IChatEngine ChatEngine
        {
           get { return this.chatEngine; }
        }

        /// <summary>
        /// Gets or sets object for formatting messages into a document
        /// </summary>
        public MessageFormatter MessageFormatter { get; set; } 

        /// <summary>
        /// Gets or sets the text that starts internal links
        /// </summary>        
        public string LinkTitlePrefix { get; set; } 

        /// <summary>
        /// Gets or sets the list of messages, filtered
        /// </summary>
        public IList<Message> FilteredMessageList { get; set; } 

        /// <summary>
        /// Gets or sets the formatting options
        /// </summary>
        public FormatOptions PrivateMessageFormatOptions { get; set; }

        /// <summary>
        /// Gets message filtering state 
        /// </summary>
        public FilterViewModel Filters { get; private set; }

        /// <summary>
        /// Gets or sets the interface to scrolling the main window to bottom
        /// </summary>
        public DocumentViewerScrollToBottom ScrollToBottom { get; set; }

        /// <summary>
        /// Gets the Source for Server settings
        /// </summary>
        public IServerSettingsRepository ServerSettingsRepository
        {
            get { return this.serverSettingsRepository; }
        }

        /// <summary>
        /// Gets or sets the style for the "send private message" button
        /// </summary>
        public Style SendPrivateMessageButtonStyle { get; set; }

        /// <summary>
        /// Gets or sets the style for the "delete message" button
        /// </summary>
        public Style DeleteMessageButtonStyle { get; set; }

        /// <summary>
        /// Gets or sets the command to show the "send private message" window
        /// </summary>
        public ICommand ShowSendPrivateMessageCommand { get; set; }

        /// <summary>
        /// Gets or sets the command to tell the application about operations completed 
        /// </summary>
        public ICommand OperationCompletedCommand { get; set; }

        #endregion

        #region methods

        /// <summary>
        /// Save the window size and position
        /// </summary>
        public void SaveWindowPosition()
        {
            UISettingsData settingsData = this.uiSettingsRepository.ReadSettings();
            settingsData.PrivateMessagesWindowDimensions = new Rect(this.Window.Left, this.Window.Top, this.Window.Width, this.Window.Height);

            this.uiSettingsRepository.SaveSettings(settingsData);
        }

        /// <summary>
        /// Load the window size and position
        /// </summary>
        public void LoadWindowPosition()
        {
            UISettingsData settingsData = this.uiSettingsRepository.ReadSettings();
            this.Window.SetWindowPosition(settingsData.PrivateMessagesWindowDimensions);
        }

        /// <summary>
        /// Read UI settings
        /// </summary>
        public void ReadSettings()
        {
            this.privateMessagesReadSettingsCommand.Execute(this);
        }

        /// <summary>
        /// Are there messages loaded
        /// </summary>
        /// <returns>true if messages have been loaded</returns>
        public bool HasMessages()
        {
            return (this.ChatEngine != null) && (this.ChatEngine.PrivateChatMessages.Count > 0);
        }

        /// <summary>
        /// Generate a message filter
        /// </summary>
        public void GenerateFilter()
        {
            this.Filters.GenerateFilterCommand.Execute(this.Filters);
            this.HasFilter = this.Filters.CurrentFilter != null;
        }

        /// <summary>
        /// Filter the messages
        /// </summary>
        public void ApplyFilter()
        {
            IList<Message> privateMessages = this.ChatEngine.PrivateChatMessages;
            this.FilteredMessageList = privateMessages.ApplyFilter(this.Filters.CurrentFilter).ToList();
        }

        /// <summary>
        /// Have the mesages been filtered
        /// </summary>
        /// <returns>true if any messages have been filtered out</returns>
        public bool MessageListIsFiltered()
        {
            return (this.FilteredMessageList != null) && 
                (this.Filters.CurrentFilter != null) && 
                (this.FilteredMessageList.Count < this.ChatEngine.PrivateChatMessages.Count);
        }

        /// <summary>
        /// Clear the fitler
        /// </summary>
        public void ClearFilter()
        {
            this.Filters.ClearFilterCommand.Execute(this.Filters);
            this.HasFilter = false;

            if (this.HasMessages())
            {
                this.DisplayMessagesData();
            }
        }

        /// <summary>
        /// Redisplay according to the current filter
        /// </summary>
        public void RefreshFilter()
        {
            this.Filters.WriteSettingsCommand.Execute(this.Filters);
            this.GenerateFilter();

            if (this.HasMessages())
            {
                this.DisplayMessagesData();
            }            
        }

        /// <summary>
        /// display private messages
        /// </summary>
        public void DisplayMessagesData()
        {
            this.displayPrivateMessagesDataCommand.Execute(this);
        }

        /// <summary>
        /// Set the font size
        /// </summary>
        /// <param name="fontSize">the new font size</param>
        public void SetFontSize(double fontSize)
        {
            this.PrivateMessageFormatOptions.FontSize = fontSize;
            this.DisplayMessagesData();
        }

        /// <summary>
        /// when the window is becoming visible
        /// </summary>
        public void WindowBecomesVisible()
        {
            if ((this.ChatEngine != null) && (this.ChatEngine.PrivateChatMessages.Count > 0))
            {
                this.ReadSettings();
                this.DisplayMessagesData();
            }
        }

        /// <summary>
        /// Request to delete the message
        /// </summary>
        /// <param name="sender">the event sender</param>
        /// <param name="message">the message to delete</param>
        public void DeleteMessageClick(object sender, Message message)
        {
            List<int> messageIds = new List<int> { message.Id };
            this.ChatEngine.MessageDelete.DeleteMessages(messageIds);
        }

        /// <summary>
        /// Launch the private message send dialog
        /// </summary>
        /// <param name="sender">the event sender</param>
        /// <param name="link">the user to send the message to</param>
        public void SendPrivateMessageClick(object sender, Link link)
        {
            ShowPrivateMessageParameters showPrivateMessageParameters = new ShowPrivateMessageParameters(
                this.Window,
                link,
                this.MessageFormatter.HyperlinkContext,
                this.OperationCompletedCommand);

            this.ShowSendPrivateMessageCommand.Execute(showPrivateMessageParameters);
        }

        #endregion

        #region private
        
        /// <summary>
        /// Private message data received
        /// </summary>
        /// <param name="sender">the message sender</param>
        /// <param name="e">the message params</param>
        private void ChatEnginePrivateMessagesReceived(object sender, EventArgs e)
        {
            // get onto the right thread
            Application.Current.Dispatcher.Invoke(
                DispatcherPriority.Normal,
                new NoArgEventHandler(this.DisplayMessagesData));
        }

        /// <summary>
        /// Engine operation completed
        /// </summary>
        /// <param name="sender">the message sender</param>
        /// <param name="e">the message params</param>
        private void ChatEngineOperationCompleted(object sender, EventArgs<OperationResult> e)
        {
            DeleteMessagesDataResult deleteMessagesDataResult = e.Value as DeleteMessagesDataResult;

            if ((deleteMessagesDataResult != null) && deleteMessagesDataResult.Success)
            {
                // messages have been deleted. Redisplay on the right thread
                Application.Current.Dispatcher.Invoke(
                    DispatcherPriority.Normal,
                    new DeleteMessagesDataResultEvent(this.OnDelete),
                    deleteMessagesDataResult);
            }
        }

        /// <summary>
        /// React to a message delete confirmation message
        /// </summary>
        /// <param name="deleteMessagesDataResult">the delete status</param>
        private void OnDelete(DeleteMessagesDataResult deleteMessagesDataResult)
        {
            this.DisplayMessagesData();
            this.StatusText = deleteMessagesDataResult.MessageOrErrorMessage;
        }

        #endregion
    }
}
